#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/atomic.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/types.h>

#include "log.h"

static DECLARE_WAIT_QUEUE_HEAD(wq);
atomic_t flag = ATOMIC_INIT(0);

struct cdev g_dev;
dev_t g_devno;

static ssize_t sleepy_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) {
    LOGI("process %d going to sleep\n", task_pid_nr(current));
    if (wait_event_interruptible(wq, atomic_dec_if_positive(&flag) >= 0)) {
        LOGI("process %d interrupted\n", task_pid_nr(current));
        return -ERESTARTSYS;
    }
    LOGI("process %d awoken\n", task_pid_nr(current));
    return 0;
}

static ssize_t sleepy_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos) {
    LOGI("process %d awakening the readers...\n", task_pid_nr(current));
    atomic_inc(&flag);
    wake_up_interruptible(&wq);
    return count;
}

const struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = sleepy_read,
    .write = sleepy_write,
};

static int __init sleepy_init(void) {
    LOGI("init\n");
    char buf[32];
    int err = 0;

    cdev_init(&g_dev, &fops);
    err = alloc_chrdev_region(&g_devno, 0, 1, "sleepy");
    if (err) {
        LOGE("alloc dev_t\n");
        return -EFAULT;
    }
    err = cdev_add(&g_dev, g_devno, 1);
    if (err) {
        LOGE("add sleepy dev\n");
        return -EFAULT;
    }

    format_dev_t(buf, g_devno);
    LOGI("add sleepy dev at %s\n", buf);
    return 0;
}
module_init(sleepy_init);

static void __exit sleepy_exit(void) {
    cdev_del(&g_dev);
    unregister_chrdev_region(g_devno, 1);
}
module_exit(sleepy_exit);

MODULE_LICENSE("GPL");